%-*-coding: utf-8 -*- 
%abs-guidec-chapter03.tex

\chapter{特殊字符}
为什么一个字符会变得特殊？当一个字符拥有了超越它本身字面意思之外的功能，那么我们认为它是一个特殊字符。

\textbf{随处可见的特殊字符}
\begin{description}
\item[\#] \hfill \\
\textbf{注释} 以 \# 开头（ \#! 除外）的行是注释，不会被执行。

%\footnotesize\colorbox{lbcolor}{\# This line is a comment.}
\begin{lstlisting}[nolol=true]
# This line is a comment.
\end{lstlisting}

注释也可以紧随在命令之后。（请注意 \# 前的空格）。

\begin{lstlisting}[nolol=true]
echo "A comment will follow." # Comment here.
#                            ^ Note whitespace before #
\end{lstlisting}

注释也可以在以空格开始的行使用。

\begin{lstlisting}[nolol=true]
	# A tab precedes this comment.
\end{lstlisting}

注释甚至可以内嵌在管道中使用。

\begin{lstlisting}[nolol=true]
nitial=( `cat "$startfile" | sed -e '/#/d' | tr -d '\n' |\
# Delete lines containing '#' comment character.
           sed -e 's/\./\. /g' -e 's/_/_ /g'` )
# Excerpted from life.sh script
\end{lstlisting}

命令不能跟随在注释之后。没有办法去在一行中断注释，仅仅为了执行同一行中的命令。使用新的一行来执行下一条命令吧。

当然，在echo语句中，一个括起的或者逃逸的 \# 并不是标识注释的开始。同样情况的还有特殊环境用作参数的 \# 和 数字常量定义的 \#。

\begin{lstlisting}[nolol=true]
echo "The # here does not begin a comment."
echo 'The # here does not begin a comment.'
echo The \# here does not begin a comment.
echo The # here begins a comment.
echo ${PATH#*:}       # Parameter substitution, not a comment.
echo $(( 2#101011 ))  # Base conversion, not a comment.
# Thanks, S.C.
\end{lstlisting}

标准的引号转义字符（ " '\textbackslash ）可以转义 \#。

一些正则匹配也会使用 \# 字符。

\item[;] \hfill \\
\textbf{命令分隔符[分号]}。这个符号允许你在同一行内放置两个或者更多的命令。

\begin{lstlisting}[nolol=true]
echo hello; echo there
if [ -x "$filename" ]; then    #  Note the space after the semicolon.
#+                   ^^
  echo "File $filename exists."; cp $filename $filename.bak
else   #                       ^^
  echo "File $filename not found."; touch $filename
fi; echo "File test complete."
\end{lstlisting}

注意：有时 ";" 需要进行转义。

\item[;;] \hfill \\
\textbf{case 语句的结束标记[双分号]}

\begin{lstlisting}[nolol=true]
case "$variable" in
  abc)  echo "\$variable = abc" ;;
  xyz)  echo "\$variable = xyz" ;;
esac
\end{lstlisting}

\item[;;\&, ;\&] \hfill \\
\textbf{case 语句的结束标记（ Bash版本 4+）}

\item[.] \hfill \\
\textbf{.命令} 等价于 source （参见Excmple 15.22）。这是一个bash 内建命令。

\item[.] \hfill \\
\textbf{文件名称分割符} 对于文件来说，一个 . 前缀标识是一个隐藏文件，ls 命令不能正常的显示这些文件。

\begin{lstlisting}[nolol=true]
bash$ touch .hidden-file
bash$ ls -l
total 10
 -rw-r--r--    1 bozo      4034 Jul 18 22:04 data1.addressbook
 -rw-r--r--    1 bozo      4602 May 25 13:58 data1.addressbook.bak
 -rw-r--r--    1 bozo       877 Dec 17  2000 employment.addressbook
bash$ ls -al
total 14
 drwxrwxr-x    2 bozo  bozo      1024 Aug 29 20:54 ./
 drwx------   52 bozo  bozo      3072 Aug 29 20:51 ../
 -rw-r--r--    1 bozo  bozo      4034 Jul 18 22:04 data1.addressbook
 -rw-r--r--    1 bozo  bozo      4602 May 25 13:58 data1.addressbook.bak
 -rw-r--r--    1 bozo  bozo       877 Dec 17  2000 employment.addressbook
 -rw-rw-r--    1 bozo  bozo         0 Aug 29 20:54 .hidden-file
\end{lstlisting}

对于目录而言，'.' 标识是当前工作目录，'..' 则用于指代上级目录。

\begin{lstlisting}[nolol=true]
bash$ pwd
/home/bozo/projects
bash$ cd .
bash$ pwd
/home/bozo/projects
bash$ cd ..
bash$ pwd
/home/bozo/
\end{lstlisting}

. 也经常作为文件移动的目的目录，在这种情况下，. 代表当前目录。
\begin{lstlisting}[nolol=true]
bash$ cp /home/bozo/current_work/junk/* .
\end{lstlisting}
拷贝所有 junk 目录下的文件到 \$PWD目录下

%
\item[.] \hfill \\
\textbf{. 字符匹配} 在字符匹配中，作为正则表达式的一部分，. 表示匹配一个字符。

%
\item["] \hfill \\
\textbf{部分引用[双引号]} "STRING" 中的大部分特殊字符不被 Shell 翻译。参考第五章。

%
\item['] \hfill \\
\textbf{全引用[单引号]} 'STRING' 中的所有特殊字符都不被 Shell 翻译。这是双引号引用 "STRING" 的增强形式。 参考第五章。

%
\item[,] \hfill \\
\textbf{逗号运算符} 逗号运算符用于连接一系列的数学运算。所有的表达式都会被求值，但是只有最后一个表达式的值会被返回。

\begin{lstlisting}[nolol=true]
let "t2 = ((a = 9, 15 / 3))"
# Set "a = 9" and "t2 = 15 / 3"
\end{lstlisting}

逗号表达式也可以用于拼接字符串。

\begin{lstlisting}[nolol=true]
for file in /{,usr/}bin/*calc
#             ^    Find all executable files ending in "calc"
#+                 in /bin and /usr/bin directories.
do
        if [ -x "$file" ]
        then
          echo $file
        fi
done
# /bin/ipcalc
# /usr/bin/kcalc
# /usr/bin/oidcalc
# /usr/bin/oocalc
# Thank you, Rory Winston, for pointing this out.
\end{lstlisting}

%
\item[,, ,] \hfill \\
\textbf{小写转换} 变量替代中的小写替换（Bash 第 4 版新增）。

%
\item[\textbackslash] \hfill \\
\textbf{转义字符[反斜线]} 对单字符的引用机制。
\textbackslash\textbf{X} 转义字符 X。效果就像 X 被引号括起来一样，等价于 'X'。\textbackslash 也可以用于 " 和 ' ，这样，就可以使用它们的字面意思。

第五章详细介绍了转义字符。
%
\item[/] \hfill \\
\textbf{文件路径分隔符[斜线]} 分隔文件名的各个部分。（比如 \verb|/home/bozo/projects/Makefile|）。

这个字符同时也是数学运算符中的除法运算符号。

%
\item[`] \hfill \\
\textbf{命令替换} \textbf{`command`} 会执行 \textbf{command} ,并且可以把命令的执行结果赋值给一个变量。这个符号也被大家称为反引号。

%
\item[:] \hfill \\
\textbf{空命令[冒号]} 这是 Shell 对 NOP （\textit{no op}, 什么也不做的操作）的等价实现。它可以认为是 shell 内建变量 true 的同义词。":" 命令也是一个 Bash内建命令，并且它的返回状态始终是 \textit{true}(0)。

\begin{lstlisting}[nolol=true]
:
echo $?   # 0
\end{lstlisting}

一个无限循环的例子：
\begin{lstlisting}[nolol=true]
while :
do
   operation-1
   operation-2
   ...
   operation-n
done
# Same as:
#    while true
#    do
#      ...
#    done
\end{lstlisting}

if/then 条件测试的占位符：
\begin{lstlisting}[nolol=true]
if condition
then :   # Do nothing and branch ahead
else     # Or else ...
   take-some-action
fi
\end{lstlisting}

作为二进制命令操作的占位符，参考 Excample 8.2 和 默认参数。
\begin{lstlisting}[nolol=true]
: ${username=`whoami`}
# ${username=`whoami`}   Gives an error without the leading :
#                        unless "username" is a command or builtin...
\end{lstlisting}

在这篇文档中作为命令的占位符。参见 Example 19.10。

对使用变量替换的字符串进行比较（参见 Ecample 10.7）：
\begin{lstlisting}[nolol=true]
: ${HOSTNAME?} ${USER?} ${MAIL?}
#  Prints error message
#+ if one or more of essential environmental variables not set.
\end{lstlisting}

变量扩展和子串替换。

与重定向操作符 > 一同使用，可以在不改变文件权限的情况下，把一个文件的内容清空。如果文件不存在，那么会创建这个文件。
\begin{lstlisting}[nolol=true]
: > data.xxx   # File "data.xxx" now empty.           
# Same effect as   cat /dev/null >data.xxx
# However, this does not fork a new process, since ":" is a builtin.
\end{lstlisting}
参考 Ecample 16-15。

与重定向操作符 >> 一同使用（\textbf{: >> target\_file}），对已经存在的文件没有影响。如果文件不存在，那么会创建此文件。
 
上两种情况是对普通文件而言，不包括管道、符号链接、以及其他特殊文件。

尽管不推荐这样做，但是你还是可以使用 : 来开始一行注释。使用 \# 来开始注释的话，会关闭 Shell 对同行后续内容的错误检查，因此几乎所有内容偶读可以出现在注释中。下面是一种不一样的情况：
\begin{lstlisting}[nolol=true]
: This is a comment that generates an error, ( if [ $x -eq 3] ).
\end{lstlisting}

":" 同时还在 /ect/passwd 和 \$PATH 变量中作为字段分隔符。
\begin{lstlisting}[nolol=true]
bash$ echo $PATH
/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/sbin:/usr/sbin:/usr/games
\end{lstlisting}

%
\item[!] \hfill \\
\textbf{取反运算符。}! 运算符对命令的退出状态取反（Example 6.2）。它也可以对测试运算符的意义进行取反操作。比如，把等于（==）改成不等于（!=）。! 运算符是一个 Bash 关键字。 

在某些环境下中，! 也可能出现在间接变量引用中。

有的上下文环境中，从命令行执行，! 标识 Bash 执行的历史命令（附录 K）。请注意在脚本环境中，! 的历史命令功能将被禁用。

%
\item[*] \hfill \\
\textbf{任意字符匹配[asterisk]。} * 在通配符可以匹配所有的文件名扩展。就其自身而言，它可以匹配指定目录下的所有文件名。
\begin{lstlisting}[nolol=true]
bash$ echo *
abs-book.sgml add-drive.sh agram.sh alias.sh
\end{lstlisting}
在正则表达式中，* 用于代表任意多个字符（包括0个字符）。

%
\item[*] \hfill \\
\textbf{数学运算符。}  在数学运算符环境中，* 表示乘。

%
\item[?] \hfill \\
\textbf{测试运算符。} 在特定表达式中，? 表示对一个条件的测试。
在双括号环境下， ? 的行为与C语言的三元运算符 ?: 相同。
\begin{lstlisting}[nolol=true]
(( var0 = var1<98?9:21 ))
#                ^ ^
# if [ "$var1" -lt 98 ]
# then
#   var0=9
# else
#   var0=21
# fi
\end{lstlisting}

在变量替换表达式中，? 用于测试一个变量的值是否已经设置。

%
\item[?] \hfill \\
\textbf{通配符。} ? 在通配文件名扩展时代表一个字符，就像在扩展的正则表达式中它也代表一个字符一样。 

%
\item[\$] \hfill \\
\textbf{变量替换（指示变量的内容）。} 

\begin{lstlisting}[nolol=true]
var1=5
var2=23skidoo
echo $var1     # 5
echo $var2     # 23skidoo
\end{lstlisting}
以 \$ 为前缀，修饰该变量，可以取得该变量的值。

%
\item[\$] \hfill \\
\textbf{行终结符。}在正则表达式中，"\$" 标识文本中一行的结束。 
%
\item[\${}] \hfill \\
\textbf{变量替换} 
%
\item[\$*, \$@] \hfill \\
\textbf{位置变量。} 
%
\item[\$?] \hfill \\
\textbf{退出状态。} \$? 标识了命令或者函数，或者脚本的退出状态。 
%
\item[\$\$] \hfill \\
\textbf{进程ID。}\$\$ 标识运行该脚本的进程 ID。
%
\item[()] \hfill \\
\textbf{命令组}
\begin{lstlisting}[nolol=true]
(a=hello; echo $a)
\end{lstlisting}

注意：一个在括号内执行的命令会启动一个子 Shell 中执行。
在括号中定义的变量，对脚本的其他部分是不可见的。父进程，脚本本身，都不能读取在子进程中（子 Shell）创建的变量。

\begin{lstlisting}[nolol=true]
a=123
( a=321; )            
echo "a = $a"   # a = 123
# "a" within parentheses acts like a local variable.
\end{lstlisting}

\textbf{数组初始化}
\begin{lstlisting}[nolol=true]
Array=(element1 element2 element3)
\end{lstlisting}
%
\item[\{xxx,yyy,zzz\}] \hfill \\
\textbf{花括号扩展}
\begin{lstlisting}[nolol=true]
echo \"{These,words,are,quoted}\"   # " prefix and suffix
# "These" "words" "are" "quoted"
cat {file1,file2,file3} > combined_file
# Concatenates the files file1, file2, and file3 into combined_file.
cp file22.{txt,backup}
# Copies "file22.txt" to "file22.backup"
\end{lstlisting}
一个命令可以作用于花括号中使用逗号分隔的多个文件。文件名称扩展会自动应用。

在花括号中，不允许出现空格，除非空格使用了引号或者进行了转义。
\begin{lstlisting}[nolol=true]
echo {file1,file2}\ :{\ A," B",' C'}
#file1 : A file1 : B file1 : C file2 : A file2 : B file2 : C
\end{lstlisting}
%
\item[\{a..z\}] \hfill \\
\textbf{增强的花括号扩展}
\begin{lstlisting}[nolol=true]
echo {a..z} # a b c d e f g h i j k l m n o p q r s t u v w x y z
# Echoes characters between a and z.
echo {0..3} # 0 1 2 3
# Echoes characters between 0 and 3.
\end{lstlisting}
{a..z} 增强扩展功能是在 Bash 第 3 版中引入的。

%
\item[{}] \hfill \\
\textbf{语句块[curly brackets]。} 也叫做内联分组，它会创建一个匿名的函数。与标准函数不同，在此语句块中定义的标量，对脚本的其他部分也是可见的。
\begin{lstlisting}[nolol=true]
bash$ { local a;
              a=123; }
bash: local: can only be used in a
function
\end{lstlisting}

\begin{lstlisting}[nolol=true]
a=123
{ a=321; }
echo "a = $a"   # a = 321   (value inside code block)
# Thanks, S.C.
\end{lstlisting}

在大括号中的代码块，可以使用 I/O 重定向输出或者输入。

\begin{lstlisting}[caption={Code blocks and I/O redirection}]
#!/bin/bash
# Reading lines in /etc/fstab.
File=/etc/fstab
{
read line1
read line2
} < $File
echo "First line in $File is:"
echo "$line1"
echo
echo "Second line in $File is:"
echo "$line2"
exit 0
# Now, how do you parse the separate fields of each line?
# Hint: use awk, or . . .
# . . . Hans-Joerg Diers suggests using the "set" Bash builtin.
\end{lstlisting}

\begin{lstlisting}[caption={Saving the output of a code block to a file}]
#!/bin/bash
# rpm-check.sh
#  Queries an rpm file for description, listing,
#+ and whether it can be installed.
#  Saves output to a file.
# 
#  This script illustrates using a code block.
SUCCESS=0
E_NOARGS=65
if [ -z "$1" ]
then
  echo "Usage: `basename $0` rpm-file"
  exit $E_NOARGS
fi  
{ # Begin code block.
  echo
  echo "Archive Description:"
  rpm -qpi $1       # Query description.
  echo
  echo "Archive Listing:"
  rpm -qpl $1       # Query listing.
  echo
  rpm -i --test $1  # Query whether rpm file can be installed.
  if [ "$?" -eq $SUCCESS ]
  then
    echo "$1 can be installed."
  else
    echo "$1 cannot be installed."
  fi  
  echo              # End code block.
} > "$1.test"       # Redirects output of everything in block to file.
echo "Results of rpm test in file $1.test"
# See rpm man page for explanation of options.
exit 0
\end{lstlisting}
与小括号不同，上述例子中，大括号中的代码不会正常开启一个子Shell。

\item[{}] \hfill \\
\textbf{文本占位符} 在 \verb|xargs -i| 之后使用。{}作为输出文本的占位符。 
\begin{lstlisting}[nolol=true]
ls . | xargs -i -t cp ./{} $1
#            ^^         ^^
# From "ex42.sh" (copydir.sh) example.
\end{lstlisting}

\item[{}\textbackslash;] \hfill \\
\textbf{路径名。}大部分情况在 find 命令中使用。这不是 Shell 内建支持。
";" 终止 find 命令执行序列中的 \verb|-exec| 选项。需要对它进行转义，以防止他被 Shell 翻译。 

\item[[]] \hfill \\
\textbf{测试。} 测试在 [] 中的表达式。注意 [ 是Shell内建测试命令的一部分，而不是外部命令 \verb|/usr/bin/test| 的链接。

\item[[[]]] \hfill \\
\textbf{测试。} 测试在 [[]] 中的表达式。这个比单括号的测试 [] 更灵活。这是一个 Shell 的关键词。

参考关于 [[]] 结构 的讨论。

\item[[]] \hfill \\
\textbf{数组元素。} 在数组上下文环境中，中括号设置数组元素的编号。
\begin{lstlisting}[nolol=true]
Array[1]=slot_1
echo ${Array[1]}
\end{lstlisting}

\item[[]] \hfill \\
\textbf{字符范围。}在正则表达式中，中括号定义匹配字符的范围。

\item[\$[\ldots]] \hfill \\
\textbf{整型扩展。}
计算在 \$[\ldots] 表达式中的的整型表达式的值。
\begin{lstlisting}[nolol=true]
a=3
b=7
echo $[$a+$b]   # 10
echo $[$a*$b]   # 21
\end{lstlisting}
注意不推荐这种做法，建议使用 ((...)) 结构。

\item[(())] \hfill \\
\textbf{整型扩展。}
扩展并且计算在 (()) 表达式中的的值。

\item[> \&> >\& >> < <>] \hfill \\
\textbf{重定向。}

\textbf{scriptname > filename} 重定向 \verb|scriptname| 的输出到文件 \verb|filename|，文件存在，则进行覆盖。

\textbf{command \&> filename} 重定向 \verb|command| 的标准输出（\verb|stdout|） 和标准错误（\verb|stderr|）到文件 \verb|filename|，文件存在，则进行覆盖。

当对某个条件进行测试的时候，希望执行强制输出，这种方法就很有用。比如，我们要测试某个命令是否存在。
\begin{lstlisting}[nolol=true]
bash$ type bogus_command &>/dev/null
bash$ echo $?
1
\end{lstlisting}

或者把它写到脚本中：
\begin{lstlisting}[nolol=true]
command_test () { type "$1" &>/dev/null; }
#                                      ^
cmd=rmdir            # Legitimate command.
command_test $cmd; echo $?   # 0
cmd=bogus_command    # Illegitimate command
command_test $cmd; echo $?   # 1
\end{lstlisting}

\textbf{command >\&2} 重定向 \verb|command| 的标准输出到标准错误。

\textbf{scriptname >> filename} 重定向 \verb|scriptname| 的输出，追加到文件 \verb|filename|，如果文件不存在，则创建文件。

\textbf{[i]<>filename} 以读写方式打开文件 \verb|filename|，并且把文件描述符 \verb|i| 赋值给它。如果文件不存在，则创建文件。

在一些情况下， "<" 和 ">" 字符可以用作字符串比较操作符。

一些情况下， "<" 和 ">" 字符可以用作整型书比较操作符。参见Example 16.9。

\item[<<] \hfill \\
\textbf{重定向操作符，参见 here document。}

\item[<<] \hfill \\
\textbf{重定向操作符，参见 here string。}

\item[<,>] \hfill \\
\textbf{ASCII比较。}
\begin{lstlisting}[nolol=true]
veg1=carrots
veg2=tomatoes
if [[ "$veg1" < "$veg2" ]]
then
  echo "Although $veg1 precede $veg2 in the dictionary,"
  echo -n "this does not necessarily imply anything "
  echo "about my culinary preferences."
else
  echo "What kind of dictionary are you using, anyhow?"
fi
\end{lstlisting}

%
\item[\textbackslash<, \textbackslash>] \hfill \\
\textbf{正则表达式中的单词分隔符。}
\begin{lstlisting}[nolol=true]
bash$ grep '\<the\>' textfile
\end{lstlisting}

%
\item[|] \hfill \\
\textbf{管道。} 传递输出（上一个命令的标准输出）作为下一个命令的输入（标准输入），或者传递给 Shell。这是把一些命令连接在一起的方法。
\begin{lstlisting}[nolol=true]
echo ls -l | sh
#  Passes the output of "echo ls -l" to the shell,
#+ with the same result as a simple "ls -l".
cat *.lst | sort | uniq
# Merges and sorts all ".lst" files, then deletes duplicate lines.
\end{lstlisting}
管道也是经典的进程间通信的方法。它把一个进程的标准输出作为另一个进程的标准输入。在典型的情况下，一个命令（比如 cat 或者 echo），会管道一个数据流给一个过滤器（\verb|fileter|），这个命令传输它的输入进行处理。
\begin{lstlisting}[nolol=true]
cat $filename1 $filename2 | grep $search_word
\end{lstlisting}
如果对 UNIX 管道的更复杂应用有兴趣，那么请参考 the UNIX FAQ, PART 3。

单个命令或者多个命令的输出可以出管道输入给一个脚本。
\begin{lstlisting}[nolol=true]
#!/bin/bash
# uppercase.sh : Changes input to uppercase.
tr 'a-z' 'A-Z'
#  Letter ranges must be quoted
#+ to prevent filename generation from single-letter filenames.
exit 0
\end{lstlisting}

现在让我们测试一下把 \textbf{ls -l} 的输出管道给上面的脚本。
\begin{lstlisting}[nolol=true]
bash$ ls -l | ./uppercase.sh
-RW-RW-R--    1 BOZO  BOZO       109 APR  7 19:49 1.TXT
 -RW-RW-R--    1 BOZO  BOZO       109 APR 14 16:48 2.TXT
 -RW-R--R--    1 BOZO  BOZO       725 APR 20 20:56 DATA-FILE
\end{lstlisting}
每一个进程的标准输出必须要要作为下一个进程的标准输入进行读取。否则，数据流将会发生阻塞（\verb|block|），那么，管道有可能不能按期望的方式工作。
\begin{lstlisting}[nolol=true]
cat file1 file2 | ls -l | sort
# The output from "cat file1 file2" disappears.
\end{lstlisting}
管道是作为子进程（child process）在运行的，因此不能用于改变脚本中变量的值。
\begin{lstlisting}[nolol=true]
variable="initial_value"
echo "new_value" | read variable
echo "variable = $variable"     # variable = initial_value
\end{lstlisting}
如果管道中的一个命令退出了，将会导致管道被终止执行。我们称其为破裂管道（\textit{broken pipe}），这种情况会发送一个 \textit{SIGPIPE} 信号。

%
\item[>|] \hfill \\
\textbf{强制重定向（即使设置了 noclobber 选项）。}这将会强制覆盖已存在的文件。

%
\item[||] \hfill \\
\textbf{或运算符（OR）。}在测试结构语句中，如果任意一个关联的测试条件为真，那么 || 运算符会返回 0（成功）。

%
\item[\&] \hfill \\
\textbf{后台运行。}命令后跟一个 \& 会把该任务提交至后台运行。
\begin{lstlisting}[nolol=true]
bash$ sleep 10 &
[1] 850
[1]+  Done                    sleep 10
\end{lstlisting}
在脚本中，命令，甚至是循环都可以在后台运行。
\begin{lstlisting}[caption={Running a loop in the background}]
#!/bin/bash
# background-loop.sh
for i in 1 2 3 4 5 6 7 8 9 10            # First loop.
do
  echo -n "$i "
done & # Run this loop in background.
       # Will sometimes execute after second loop.
echo   # This 'echo' sometimes will not display.
for i in 11 12 13 14 15 16 17 18 19 20   # Second loop.
do
  echo -n "$i "
done  
echo   # This 'echo' sometimes will not display.
# ======================================================
# The expected output from the script:
# 1 2 3 4 5 6 7 8 9 10 
# 11 12 13 14 15 16 17 18 19 20 
# Sometimes, though, you get:
# 11 12 13 14 15 16 17 18 19 20 
# 1 2 3 4 5 6 7 8 9 10 bozo $
# (The second 'echo' doesn't execute. Why?)
# Occasionally also:
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
# (The first 'echo' doesn't execute. Why?)
# Very rarely something like:
# 11 12 13 1 2 3 4 5 6 7 8 9 10 14 15 16 17 18 19 20 
# The foreground loop preempts the background one.
exit 0
#  Nasimuddin Ansari suggests adding    sleep 1
#+ after the   echo -n "$i"   in lines 6 and 14,
#+ for some real fun.
\end{lstlisting}
脚本中一个在后台运行的命令可能会导致脚本挂起，它会一直等待一次键盘事件。所幸，有一种办法可以解决这个问题。

\item[\&\&] \hfill \\
\textbf{与运算符（AND）。}在一个测试环境中，所有关联的测试条件为真的时候，\&\& 运算符返回 0（成功）。

\item[-] \hfill \\
\textbf{选项，前缀。}命令或者过滤器的选项标志。或者是运算符的前缀。在变量替换中中，作为默认参数的前缀。
\textbf{COMMAND -[Option1][Option2][\ldots]}

\textbf{ls -al}

\textbf{sort -dfu \$filename}

\begin{lstlisting}[nolol=true]
if [ $file1 -ot $file2 ]
then #      ^
  echo "File $file1 is older than $file2."
fi
if [ "$a" -eq "$b" ]
then #    ^
  echo "$a is equal to $b."
fi
if [ "$c" -eq 24 -a "$d" -eq 47 ]
then #    ^              ^
  echo "$c equals 24 and $d equals 47."
fi
param2=${param1:-$DEFAULTVAL}
#               ^
\end{lstlisting}

\textbf{--}

双破折线，命令长选项的前缀。

\textbf{sort --ignore-leading-blanks}

在 Bash 内建中使用，标识对某个特定命令的选项结束符号。

下面的例子显示了如何移除那些以 “-” 开头的的文件：
\begin{lstlisting}[nolol=true]
bash$ ls -l
-rw-r--r-- 1 bozo bozo 0 Nov 25 12:29 -badname
bash$ rm -- -badname
bash$ ls -l
total 0
\end{lstlisting}
双破折线也在集合（set）关联中使用。
\textbf{set -- \$variable}（Example 15.18）。

\item[-] \hfill \\
\textbf{重定向标准输入输出。} 从标准输入或标准输出重定向，或者重定向到标准输入或者标准输出。
\begin{lstlisting}[nolol=true]
bash$ cat -
abc
abc
...
Ctl-D
\end{lstlisting}

与我们的期望一致，textbf{cat -} 回显了标准输入，这种情况下，用户的键盘输入，会输出到标准输出。但是，使用 '-' 的 I/O 重定向是否具有同样的效果呢？
\begin{lstlisting}[nolol=true]
(cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xpvf -)
# Move entire file tree from one directory to another
# [courtesy Alan Cox <a.cox@swansea.ac.uk>, with a minor change]
# 1) cd /source/directory
#    Source directory, where the files to be moved are.
# 2) &&
#   "And-list": if the 'cd' operation successful,
#    then execute the next command.
# 3) tar cf - .
#    The 'c' option 'tar' archiving command creates a new archive,
#    the 'f' (file) option, followed by '-' designates the target file
#    as stdout, and do it in current directory tree ('.').
# 4) |
#    Piped to ...
# 5) ( ... )
#    a subshell
# 6) cd /dest/directory
#    Change to the destination directory.
# 7) &&
#   "And-list", as above
# 8) tar xpvf -
#    Unarchive ('x'), preserve ownership and file permissions ('p'),
#    and send verbose messages to stdout ('v'),
#    reading data from stdin ('f' followed by '-').
#
#    Note that 'x' is a command, and 'p', 'v', 'f' are options.
#
# Whew!
# More elegant than, but equivalent to:
#   cd source/directory
#   tar cf - . | (cd ../dest/directory; tar xpvf -)
#
#     Also having same effect:
# cp -a /source/directory/* /dest/directory
#     Or:
# cp -a /source/directory/* /source/directory/.[^.]* /dest/directory
#     If there are hidden files in /source/directory.
\end{lstlisting}
\begin{lstlisting}[nolol=true]
bunzip2 -c linux-2.6.16.tar.bz2 | tar xvf -
#  --uncompress tar file--      | --then pass it to "tar"--
#  If "tar" has not been patched to handle "bunzip2",
#+ this needs to be done in two discrete steps, using a pipe.
#  The purpose of the exercise is to unarchive "bzipped" kernel source.
\end{lstlisting}
注意这里的 "-" 并不是 Bash 运算符，而只是某些 Unix 工具用以辨别输出到标准输出的选项参数。比如 \textbf{tar，cat，} 等等。
\begin{lstlisting}[nolol=true]
bash$ echo "whatever" | cat -
whatever
\end{lstlisting}
当需要一个文件名时，``-'' 重定向输出到标准输出（有时可以见到跟 \textbf{tar cf} 一起使用），或者接受从标准输入传入的输入，而不是从一个文件。这是在管道中使用以文件为目的工具作为过滤器的一种方法。
\begin{lstlisting}[nolol=true]
bash$ file
Usage: file [-bciknvzL] [-f namefile] [-m magicfiles] file...
\end{lstlisting}
单独使用文件命令 file，会执行失败并伴有错误信息。

添加一个 ``-'' 可以得到更加有用的结果。这种情况下， shell 会等待用户的输入。
\begin{lstlisting}[nolol=true]
bash$ file -
abc
standard input:              ASCII text
bash$ file -
#!/bin/bash
standard input:              Bourne-Again shell script text executable
\end{lstlisting}
现在这个命令接受从标准输入的输入并且能够进行解析。

``-'' 可以通过管道的标准输出传递给其他命令。这就允许我们执行一些比如在文件开头处增加一些行的特殊功能。

使用 diff 来比较一个文件和另一个文件的一部分：
\begin{lstlisting}[nolol=true]
grep Linux file1 | diff file2 -
\end{lstlisting}
最后，看一个真实的 tar 程序使用 ``-'' 的例子：
\begin{lstlisting}[caption={备份所有昨天发生改变的文件}]
#!/bin/bash
#  Backs up all files in current directory modified within last 24 hours
#+ in a "tarball" (tarred and gzipped file).
BACKUPFILE=backup-$(date +%m-%d-%Y)
#                 Embeds date in backup filename.
#                 Thanks, Joshua Tschida, for the idea.
archive=${1:-$BACKUPFILE}
#  If no backup-archive filename specified on command-line,
#+ it will default to "backup-MM-DD-YYYY.tar.gz."
tar cvf - `find . -mtime -1 -type f -print` > $archive.tar
gzip $archive.tar
echo "Directory $PWD backed up in archive file \"$archive.tar.gz\"."
#  Stephane Chazelas points out that the above code will fail
#+ if there are too many files found
#+ or if any filenames contain blank characters.
# He suggests the following alternatives:
# -------------------------------------------------------------------
#   find . -mtime -1 -type f -print0 | xargs -0 tar rvf "$archive.tar"
#      using the GNU version of "find".
#   find . -mtime -1 -type f -exec tar rvf "$archive.tar" '{}' \;
#         portable to other UNIX flavors, but much slower.
# -------------------------------------------------------------------
exit 0
\end{lstlisting}

以 ``-'' 作为文件名开头的文件在和 ``-''一起使用时可能会引起问题。脚本应该检测这种情况，并且对这些文件名加上合适的前缀。比如 \verb|/-FILENAME，$PWD/-FILENAME|，或者 \verb|$PATHNAME/-FILENAME|。

如果变量的值以 ``-'' 开头，同样也存在问题。
\begin{lstlisting}[nolol=true]
var="-n"
echo $var               
# Has the effect of "echo -n", and outputs nothing.
\end{lstlisting}

\item[-] \hfill \\
\textbf{前工作目录。} textbf{cd -} 命令会切换目录为上一个工作目录。这种行为使用到了 \$OLDPWD 环境变量。

不要被这里讨论的 ``-'' 和刚刚讨论的重定向操作符搞混淆了，``-'' 该如何翻译，取决于它的上下文环境了。

\item[-] \hfill \\
\textbf{减号。}数学运算中的减号。

\item[=] \hfill \\
\textbf{等号。}赋值运算符。
\begin{lstlisting}[nolol=true]
a=28
echo $a   # 28
\end{lstlisting}
在其他环境下，``='' 还有可能表示字符串比较操作。

\item[+] \hfill \\
\textbf{加号。}加法运算符。
在其他环境下，``+'' 还可能是正则表达式的运算符。

\item[+] \hfill \\
\textbf{选项。}命令或者过滤器的选项标志。

一些命令和内建操作使用 ``+'' 来开启特定选项，使用 ``-'' 来关闭这些选项。在变量替换中，``+''用于作为变量的备用值的前缀。 

\item[\%] \hfill \\
\textbf{模（modulo）。} 模数学运算。
\begin{lstlisting}[nolol=true]
let "z = 5 % 3"
echo $z  # 2
\end{lstlisting}
在其他环境中，% 有可能表示模式匹配运算符。

\item[\~] \hfill \\
\textbf{家目录。} 对应的是 textbf{\$HOME} 环境变量。\verb|~bobo| 是 bozo 的家目录，\textbf{ls ~bozo} 可以列出这个目录的内容。~/ 是当前用户的家目录，textbf{ls ~/} 可以列出目录的内容。
\begin{lstlisting}[nolol=true]
bash$ echo ~bozo
/home/bozo
bash$ echo ~
/home/bozo
bash$ echo ~/
/home/bozo/
bash$ echo ~:
/home/bozo:
bash$ echo ~nonexistent-user
~nonexistent-user
\end{lstlisting}
\item[\~+] \hfill \\
\textbf{当前工作目录。} 等价于内部变量 \$PWD。
\begin{lstlisting}[nolol=true]
\end{lstlisting}
\item[\~-] \hfill \\
\textbf{前工作目录。} 等价于内部变量 \$OLDPWD。
\item[=\~] \hfill \\
\textbf{正则表达式匹配。} Bash 第 3 版中引入。
\item[\^{}] \hfill \\
\textbf{行开始。} 正则表达式中，``-''表示文本中一行的开始。
\item[\^{},\^{}\^{}] \hfill \\
\textbf{大写转换。} 变量替换中的大写转换。Bash 第 4 版中增加。
\item[控制字符] \hfill \\
\textbf{改变终端或者文件显示的当前行为。} 特殊字符就是 \textbf{CONTROL + 键} 的组合。控制字符也可以写成转义字符加 8 进制或者 16 进制符号的形式。

在脚本内，控制字符一般用处不大。

\begin{description}
\item[Ctl-A] \hfill \\
移动光标到行首（命令行下可用）。
\item[Ctl-B] \hfill \\
无损退格。
\item[Ctl-C] \hfill \\
中断。停止前台执行的作业。
\item[Ctl-D] \hfill \\
从 shell 注销登录。（与 exit 类似。）

\textbf{EOF} 文件结束符。中断从标准输入读入的输入。
当在 控制台或者 xterm 窗口模式下输入文本时， \textbf{Ctl-D} 擦除光标下的字符。如果没有不存在任何字符，那么就退出绘画。在 xterm 窗口中，这对于关闭窗口没有作用。
\item[Ctl-E] \hfill \\
移动光标到行尾（命令行有效）。
\item[Ctl-F] \hfill \\
前移一个字符（命令行有效）。
\item[Ctl-G] \hfill \\
\textbf{BEL。}在一些较老的终端上，会触发铃声。在 xterm 下，可能会 beep。
\item[Ctl-H] \hfill \\
\textbf{Rubout。} （破坏性退格。）退格的时候同时擦除退格后光标所在的字符。
\begin{lstlisting}[nolol=true]
#!/bin/bash
# Embedding Ctl-H in a string.
a="^H^H"                  # Two Ctl-H's -- backspaces
                          # ctl-V ctl-H, using vi/vim
echo "abcdef"             # abcdef
echo
echo -n "abcdef$a "       # abcd f
#  Space at end  ^              ^  Backspaces twice.
echo
echo -n "abcdef$a"        # abcdef
#  No space at end               ^ Doesn't backspace (why?).
                          # Results may not be quite as expected.
echo; echo
# Constantin Hagemeier suggests trying:
# a=$'\010\010'
# a=$'\b\b'
# a=$'\x08\x08'
# But, this does not change the results.
\end{lstlisting}
\item[Ctl-I] \hfill \\
水平 tab。
\item[Ctl-J] \hfill \\
新行。脚本中可以写作 8 进制的形式 `\textbackslash{}012' 或者 16 进制的形式 `\textbackslash{}x0a'。 
\item[Ctl-K] \hfill \\
\textbf{垂直 tab。}

在字符控制台或者 xterm 窗口输入文本时， \textbf{Ctl-K} 擦除从光标下字符到行尾的字符。在脚本中，\textbf{Ctl-K} 的行为可能会有所不同，参考下面 Lee Lee
Maschmeyer's 的例子。
\item[Ctl-L] \hfill \\
\textbf{换页。}清屏。在终端中，这个快捷键和 clear 命令的作用相同。当送往打印机是，\textbf{Ctl-L} 会引起打印移到到纸张的结束处。
\item[Ctl-M] \hfill \\
回车。
\begin{lstlisting}[nolol=true]
#!/bin/bash
# Thank you, Lee Maschmeyer, for this example.
read -n 1 -s -p \
$'Control-M leaves cursor at beginning of this line. Press Enter. \x0d'
           # Of course, '0d' is the hex equivalent of Control-M.
echo >&2   #  The '-s' makes anything typed silent,
           #+ so it is necessary to go to new line explicitly.
read -n 1 -s -p $'Control-J leaves cursor on next line. \x0a'
           #  '0a' is the hex equivalent of Control-J, linefeed.
echo >&2
###
read -n 1 -s -p $'And Control-K\x0bgoes straight down.'
echo >&2   #  Control-K is vertical tab.
# A better example of the effect of a vertical tab is:
var=$'\x0aThis is the bottom line\x0bThis is the top line\x0a'
echo "$var"
#  This works the same way as the above example. However:
echo "$var" | col
#  This causes the right end of the line to be higher than the left end.
#  It also explains why we started and ended with a line feed --
#+ to avoid a garbled screen.
# As Lee Maschmeyer explains:
# --------------------------
#  In the [first vertical tab example] . . . the vertical tab
#+ makes the printing go straight down without a carriage return.
#  This is true only on devices, such as the Linux console,
#+ that can't go "backward."
#  The real purpose of VT is to go straight UP, not down.
#  It can be used to print superscripts on a printer.
#  The col utility can be used to emulate the proper behavior of VT.
exit 0
\end{lstlisting}
\item[Ctl-N] \hfill \\
擦除历史缓冲区中的一行。（命令行下使用）
\item[Ctl-O] \hfill \\
开始新行。（命令行下使用）
\item[Ctl-P] \hfill \\
从历史缓冲区执行最近的命令。（命令行使用）
\item[Ctl-Q] \hfill \\
恢复执行。\textbf{XON}。
恢复终端中的标准输入。
\item[Ctl-R] \hfill \\
在历史缓冲区中反向查找。（命令行模式使用）
\item[Ctl-S] \hfill \\
挂起。\textbf{XOFF}

这个会冻结当前的标准输入。（使用 Ctl-Q 恢复输入）
\item[Ctl-T] \hfill \\
交换光标所在处的字符和前一个位置的字符。（命令行使用）
\item[Ctl-U] \hfill \\
擦除当前输入行，从光标所在处到行开始位置。有些设置中，\textbf{Ctl-U} 擦除一整行，而不考虑当前光标的位置。
\item[Ctl-V] \hfill \\
输入文本时，\textbf{Ctl-V}允许插入控制字符。例如，下面这两种是等价的：
\begin{lstlisting}[nolol=true]
echo -e '\x0a'
echo <Ctl-V><Ctl-J>
\end{lstlisting}
\textbf{Ctl-V}在文本编辑器中也基本上是有用的。
\item[Ctl-W] \hfill \\
当在控制台或者 xterm 窗口中中输入文本时，\textbf{Ctl-W}擦除光标下的字符，并退格至第一个回退空白处。在某些设置中，\textbf{Ctl-W}擦除退格至第一个非文字数字的字符。
\item[Ctl-X] \hfill \\
在某些字处理系统中，剪切高亮的文本并复制到剪切板。
\item[Ctl-Y] \hfill \\
粘贴之前擦除的文本。（使用 Ctl-U 或者 Ctl-W）
\item[Ctl-Z] \hfill \\
停止一项前台的作业。

在某些文字处理程序中执行替换操作。

MSDOS文件系统中的文件结束符号（\textbf{EOF}）。
\end{description}

\item[空白字符（Whitespace）] \hfill \\
命令、变量之间的分隔符号。空白字符包括空格（spaces）、tab、空行，以及任何相关的组合形式。在一些上下文环境中，可能不允许出现空白符（比如变量赋值），否则会引起声明错误。

空行对脚本执行没有影响，因此常常被用作可视化地分隔函数功能模块。

\textbf{\$IFS}，某些命令分隔字段的特殊变量。默认的是空格（whitespace）。

\textbf{定义：} \textbf{字段（field）}是一些以字符串或者连续的字符为表现形式的不连贯的数据块。字段和字段之间通过空白字符其他设定的字符来分隔（通常是 \$IFS）。在一些上下文环境中，字段也称做记录（record）。

如果要保留字符字符串或者变量中的空白字符，请使用引号（quoting）。

UNIX 过滤器可以使用 POSIX字符类 [:space:] 定位和操作空白字符。
\end{description}
